﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ProceduralCity.Textures;
using ProceduralCity.Textures.Walls;
using ProceduralCity.Utility;

namespace ProceduralCity.Models.Buildings
{
    class RectangularBuilding : BuildingBase
    {
        /// <summary>
        /// {near, right, far, left} when facing Vector3.Forward.
        /// </summary>
        private TexturedQuad[] walls;
        private TexturedQuad roof;

        #region Static Random Generators

        // TODO: the probability of a building having maximum or minimum width is 0-50% less likely than any other value in the middle. Consider fixing.

        /// <summary>
        /// Randomly generates a building centered at the origin, between the specified sizes, and snapped to a chunkSize.
        /// </summary>
        public static RectangularBuilding RandomGenerateCentered(Vector3 minSize, Vector3 maxSize, Vector3 chunkSize)
        {
            // Offsetting minSize and maxSize by (chunkSize / 2) makes it so snapping to a chunk size stays within the bounds of [minSize, maxSize].
            Vector3 buildingSize = Maths.NearestMultiple(Maths.RandomBetween(minSize + chunkSize / 2, maxSize - chunkSize / 2), chunkSize);
            RectangularBuilding building = new RectangularBuilding(buildingSize);
            return building;
        }

        /// <summary>
        /// Randomly generates a building containing (but potentially off-center from) the origin, fitting in a lot of the given size, and snapped to a chunkSize.
        /// </summary>
        public static RectangularBuilding RandomGenerate(Vector3 lotSize, Vector3 chunkSize)
        {
            Vector3 lotMin = new Vector3(-lotSize.X / 2, 0, -lotSize.Z / 2);
            Vector3 lotMax = new Vector3(lotSize.X / 2, lotSize.Y, lotSize.Z / 2);

            // Offsetting lotMin and lotMax by (chunkSize / 2) makes it so snapping to a chunk size stays within the bounds of [lotMin, lotMax].
            lotMin += chunkSize / 2;
            lotMax -= chunkSize / 2;

            Vector3 buildingMin = Maths.NearestMultiple(Maths.RandomBetween(lotMin, Vector3.Zero - chunkSize / 2), chunkSize);
            Vector3 buildingMax = Maths.NearestMultiple(Maths.RandomBetween(Vector3.Zero + chunkSize / 2, lotMax), chunkSize);
            BoundingBox boxBounds = new BoundingBox(buildingMin, buildingMax);
            RectangularBuilding building = new RectangularBuilding(boxBounds);

            return building;
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a rectangular building centered at the origin and oriented in the cardinal directions.
        /// </summary>
        public RectangularBuilding(Vector3 size)
            :this(new BoundingBox(
                new Vector3(-size.X / 2, 0, -size.Z / 2),
                new Vector3(size.X / 2, size.Y, size.Z / 2)
            ))
        { }

        /// <summary>
        /// Creates a rectangular building with model origin at the world origin.
        /// </summary>
        /// <param name="boxBounds">Defines the exact corners of the building to generate in model space.</param>
        public RectangularBuilding(BoundingBox boxBounds)
        {
            this.Position = Vector3.Zero;
            this.BoxBounds = boxBounds;

            Vector3[] corners = BoxBounds.GetCorners();
            Vector3 nearUpperLeft = corners[0]; // when looking forwards
            Vector3 rightUpperLeft = corners[1]; // when looking left
            Vector3 farUpperLeft = corners[5]; // when looking backwards
            Vector3 leftUpperLeft = corners[4]; // when looking right

            walls = new TexturedQuad[4];
            walls[0] = new TexturedQuad(nearUpperLeft, Vector3.Backward, Vector3.Up, Size.X, Size.Y);
            walls[1] = new TexturedQuad(rightUpperLeft, Vector3.Right, Vector3.Up, Size.Z, Size.Y);
            walls[2] = new TexturedQuad(farUpperLeft, Vector3.Forward, Vector3.Up, Size.X, Size.Y);
            walls[3] = new TexturedQuad(leftUpperLeft, Vector3.Left, Vector3.Up, Size.Z, Size.Y);

            float buildingPerimeter = 2 * Size.X + 2 * Size.Z;
            SetWallTextureCoordinates(0, Size.X / buildingPerimeter, 0, Size.Z / buildingPerimeter, 0, 1);

            roof = new TexturedQuad(leftUpperLeft, Vector3.Up, Vector3.Forward, Size.X, Size.Z);
        }

        #endregion //Constructors

        /// <summary>
        /// Set texture coordinates in texture space [0, 1].
        /// If building is part of a larger structure with a shared texture, values would represent the building's coordinates as a percentage of the whole, larger structure.
        /// Recall that texture coordinates grow left -> right, and top -> bottom.
        /// </summary>
        public void SetWallTextureCoordinates(float left, float width, float near, float depth, float top, float height)
        {
            float bottom = top + height;
            walls[0].SetTextureCoordinates(new Vector2(left + near, top), new Vector2(left + width + near, bottom));
            walls[1].SetTextureCoordinates(new Vector2(left + width + near, top), new Vector2(left + width + near + depth, bottom));
            walls[2].SetTextureCoordinates(new Vector2(left + width + near + depth, top), new Vector2(left + 2 * width + near + depth, bottom));
            walls[3].SetTextureCoordinates(new Vector2(left + 2 * width + near + depth, top), new Vector2(left + 2 * width + near + 2 * depth, bottom));
        }

        protected override void DrawInternal(GraphicsDevice device, BasicEffect buildingEffect)
        {
            buildingEffect.Texture = wallTexture;
            foreach (EffectPass pass in buildingEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                foreach (TexturedQuad quad in walls)
                {
                    device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
                        quad.Vertices, 0, quad.Vertices.Length,
                        quad.Indices, 0, quad.Indices.Length / 3);
                }
            }

            buildingEffect.Texture = roofTexture;
            foreach (EffectPass pass in buildingEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
                    roof.Vertices, 0, roof.Vertices.Length,
                    roof.Indices, 0, roof.Indices.Length / 3);
            }
        }
    }
}
